package World;
import java.util.ArrayList;
import java.util.Random;

import Environment.*;
import Population.*;


public class World {
	public static Inhabitant[] population;
	
	public Cell[][] grid;
	public Cell[][] gridOld;
	
	public static Environment[][] environGrid;
	public static Environment tempEnviron;
	Random generator;
	public static int num;
	public static int cellSize;
	 int[] environmentRatioDEF = {970,30,1,15};//{grass,water, forest, desert} {970,30,1,15}
	 int[] environmentRatio = {0,0,0,0}; 
	 int[] envSimilarFactor = {4400,6500,30,25};//{4400,5700,35,25}
	 int[] envDecayFactor = {1,1200,6,7}; // {1,1,8,7}
	 int[] envRepeat = {0,0,0,0};
	 int[] envSimilar = {0,0,0,0};
	 int environmentSeed = 0;
	
	/**
	 * General initialization
	 * @param x - width of the world area
	 * @param y - height of the world area
	 * @param genLimit - 
	 * @param CellSize
	 */
	public World(int x, int y, int genLimit, int CellSize, int numPop){
		generator = new Random();
		x = x / CellSize;
		y = y / CellSize;
		cellSize = CellSize;
		grid = new Cell[y][x]; 
		gridOld = new Cell[y][x];
		environGrid = new Environment[y][x];
		population = new Inhabitant[numPop];
		int randomFactor;
		
		
		// Add more random to the generation
		randomFactor = 5 - generator.nextInt(10);
		environmentRatioDEF[0] = environmentRatioDEF[0] - randomFactor;
		environmentRatioDEF[1] = environmentRatioDEF[1] + randomFactor;
		
		randomFactor = 250 - generator.nextInt(500);
		envSimilarFactor[0] = envSimilarFactor[0] - randomFactor;
		
		randomFactor = 100 - generator.nextInt(200);
		envSimilarFactor[1] = envSimilarFactor[1] + randomFactor;
		
		randomFactor = 5 - generator.nextInt(10);
		envSimilarFactor[2] = envSimilarFactor[2] + randomFactor;
		
		randomFactor = 50 - generator.nextInt(100);
		envDecayFactor[1] = envDecayFactor[1] - randomFactor;
		
		randomFactor = 3 - generator.nextInt(6);
		envDecayFactor[2] = envDecayFactor[2] - randomFactor;
		
		
		for(int i=0; i<x; i++)
		{
			for(int j=0; j<y; j++)
			{
				// generate the environment
				environmentRatio[0] = environmentRatioDEF[0];
				environmentRatio[1] = environmentRatioDEF[1];
				envSimilar[0] = CreationFindSimilar(environGrid,j,i,"Grass");
				envSimilar[1] = CreationFindSimilar(environGrid,j,i,"Water");
				environmentRatio[0] = envSimilar[0]*envSimilarFactor[0] + environmentRatio[0] - envRepeat[0]*envDecayFactor[0];
				if(environmentRatio[0]<=0)
					environmentRatio[0]=1;
				environmentRatio[1] = envSimilar[1]*envSimilarFactor[1] + environmentRatio[1] - envRepeat[1]*envDecayFactor[1];
				if(environmentRatio[1]<=0)
					environmentRatio[1]=1;
				environmentSeed = environmentRatio[0] + environmentRatio[1];
				//System.out.println(j+","+i+"    "+envSimilar[0]+","+envSimilar[1]+"    "+environmentRatio[0]+","+environmentRatio[1]+"      "+envRepeat[0]+","+envRepeat[1]+ "     "+environmentSeed);
				num = generator.nextInt(environmentSeed);
				
				if(num<environmentRatio[0]) // generate grass
				{
					environGrid[j][i]= new Grass(i,j);
					envRepeat[0]++;
					envRepeat[1]=0;
				}
				else if(num<(environmentRatio[0]+environmentRatio[1]))
				{
					environGrid[j][i]= new Water(i,j);
					envRepeat[0]=0;
					envRepeat[1]++;
				}
				else
				{
					environGrid[j][i]= new Grass(i,j);
					envRepeat[0]++;
					envRepeat[1] = 0;
				}			
			}
		}
		RemoveLonelyBlock("Grass");
		RemoveLonelyBlock("Water");
		SmoothWater();
		RemoveLonelyBlock("Water");
		RemoveLonelyBlock("Grass");
		PostInitTerrain();
		GenerateInhabitants(x,y,genLimit,numPop);
		//GeneratePopulation(x,y,genLimit);

	}
	
	/**
	 * Makes a copy of the current population grid.
	 */
	private void CopyGrid()
	{
		for(int i=0; i<grid.length; i++)
		{
			for(int j=0; j<grid[0].length; j++)
			{
				gridOld[i][j] = new Cell(grid[i][j].x, grid[i][j].y, grid[i][j].isAlive);
				
			}
		}
	}
	
	/**
	 * Runs all timer operations in the world.
	 */
	public static void Update()
	{
		/*
		CopyGrid();
		int friends;
		for(int i=0; i<grid.length; i++)
		{
			for(int j=0; j<grid[i].length; j++)
			{
				friends = gridOld[i][j].FindFriends(gridOld);
				if(friends<2)
				{
					grid[i][j].Die();
				}
				else if(friends>3)
				{
					grid[i][j].Die();
				}
				else
				{
					if(friends==3 && environGrid[i][j].isHabitable)
					{
						grid[i][j].Born();
					}
				}
			}
		}*/
		for (int i=0; i<population.length; i++)
		{
			population[i].AI();
		}
	}
	
	/**
	 * Finds and returns the cell at that point.  Used for population.
	 * @param posX - x position
	 * @param posY - y position
	 * @return - cell
	 */
	public Cell FindCell(int posX, int posY)
	{
		int locX = posX/cellSize;
		int locY = posY/cellSize;
		return grid[locY][locX];	
	}
	
	/**
	 * checks for an inhabitant at position x and y.
	 * @param posX
	 * @param posY
	 * @return inhabitant or null if not found
	 */
	public Inhabitant FindInhabitant(int posX, int posY)
	{
		for(int i=0; i<population.length; i++)
		{
			if(population[i].getX()==posX && population[i].getY()==posY)
			{
				return population[i];
			}
		}
		return new Null_Inhab();
	}
	
	public static boolean InhabitantCollision(int posX, int posY)
	{
		for(int i=0; i<population.length; i++)
		{
			if(population[i].getX()==posX && population[i].getY()==posY)
			{
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Returns the populace world.
	 * @return returns the populace world.
	 */
	public Cell[][] GetWorld()
	{
		return grid;
	}
	
	/**
	 * Returns the list of inhabitants
	 * @return
	 */
	public Inhabitant[] GetInhabitants()
	{
		return population;
	}
	
	/**
	 * Finds a similar number of blocks to tempName during creation.  Only checks what has already been made.
	 * @param world - the environment grid
	 * @param y - y position
	 * @param x - x position
	 * @param tempName - name of the terrain
	 * @return - number of similar blocks
	 */
	private int CreationFindSimilar(Environment[][] world,int y,int x, String tempName)
	{
		int simCount = 0;
		for(int i=x-1; i<x+1; i++)
		{
			for(int j=y-1; j<=y+1; j++)
			{
				if(i>=world[0].length || j>=world.length )
				{
				}
				else if (i<0 || j<0)
				{
				}
				else if(i>=x && j>=y)
				{
					i=x;
					j=y+1;
				}
				else
				{
					if(world[j][i].name.contains(tempName))
					{
						simCount++;
					}
				}
			}
		}
		return simCount;
	}
	
	/**
	 * Find similar blocks to tempName all around (DO NOT USE DURING FIRST INIT)
	 * @param world - the environment
	 * @param y - y position
	 * @param x - x position
	 * @param tempName - name of the block to look for
	 * @return
	 */
	public int FindSimilar(Environment[][] world,int y,int x, String tempName)
	{
		int simCount = 0;
		for(int i=x-1; i<=x+1; i++)
		{
			for(int j=y-1; j<=y+1; j++)
			{
				if(i>=world[0].length || j>=world.length )
				{
				}
				else if (i<0 || j<0)
				{
				}
				else
				{
					if(world[j][i].name.contains(tempName))
					{
						simCount++;
					}
				}
			}
		}
		return simCount;
	}
	
	/**
	 * Find blocks that are not similar to the given name
	 * @param world - the environment
	 * @param y - y position
	 * @param x - x position
	 * @param tempName - name of the block to look for 
	 * @return
	 */
	public int FindNotSimilar(Environment[][] world,int y,int x, String tempName)
	{
		int simCount = 0;
		for(int i=x-1; i<=x+1; i++)
		{
			for(int j=y-1; j<=y+1; j++)
			{
				if(i>=world[0].length || j>=world.length )
				{
				}
				else if (i<0 || j<0)
				{
				}
				else
				{
					if(!world[j][i].name.contains(tempName))
					{
						simCount++;
					}
				}
			}
		}
		return simCount;
	}
	
	/**
	 * Post initial terrain generation.  Used after basic land and water are created.
	 */
	public void PostInitTerrain()
	{
		AddDesert();
		RemoveLonelyBlock("Desert");
		AddForest();
		RemoveLonelyBlock("Forest");
	}
	
	/**
	 * Initialize deserts.
	 * 
	 */
	public void AddDesert()
	{
		Random rGen= new Random();
		int num;
		
		

		for(int i=0; i<environGrid[0].length; i++)
		{
			for(int j=0; j<environGrid.length; j++)
			{
				num = rGen.nextInt(10);
				if(DistanceToEnvironment(i,j, "Water") > (20+num))
				{
					environGrid[j][i] = new Desert(i,j);
				}
			}
		}
	}
	
	/**
	 * Initialize forests.
	 */
	public void AddForest()
	{
		Random rGen= new Random();
		int num, num2;
		num2 = 1-rGen.nextInt(2);
		for(int i=0; i<environGrid[0].length; i++)
		{
			for(int j=0; j<environGrid.length; j++)
			{
				envSimilar[2] = CreationFindSimilar(environGrid,j,i, "Forest");
				environmentRatio[2] = environmentRatioDEF[2];
				environmentRatio[2] = envSimilar[2]*envSimilarFactor[2] + environmentRatio[2] - envRepeat[2]*envDecayFactor[2];
				if(environmentRatio[2]<=0)
					environmentRatio[2]=1;
				num = rGen.nextInt(100);
				if(num < environmentRatio[2] && environGrid[j][i].isHabitable && DistanceToEnvironment(i,j, "Desert")>(4-num2))
				{
					envRepeat[2]++;
					environGrid[j][i] = new Forest(i,j);
				}
				else
				{
					envRepeat[2] = 0;
				}
			}
		}
	}
	
	/**
	 * Smoothes out the water procs to make them look more like real lakes/streams and the like
	 */
	public void SmoothWater()
	{
		Random rGen= new Random();
		int num, xDist, yDist;
		double numY, numX;
		double Dist, factor, factor2, factor3, factor4, factorX, factorY;
		ArrayList<Integer> waterXArray = new ArrayList<Integer>();
		ArrayList<Integer> waterYArray = new ArrayList<Integer>();
		int count =0;
		num = rGen.nextInt(1);
		int numLoops = 2 + num;
		num = rGen.nextInt(10);
		if (num == 10)
			num = 1;
		else
			num = 0;
		int y0 = 1000;
		int offset = 1;
		// Find the water nodes.
		for (int w=0; w<1+num; w++)
		{
			for(int i=0; i<environGrid[0].length; i++)
			{
				for(int j=0; j<environGrid.length; j++)
				{
					if(environGrid[j][i].name.contains("Water") && FindNotSimilar(environGrid,j,i,"Water") >= 1)
					{
						waterXArray.add(i);
						waterYArray.add(j);
						count++;
					}
				}
			}
			
			// Proc more water!
			
			for (int m=0; m<numLoops; m++)
			{
				
				for(int n=0; n<count; n++)
				{
					numY = rGen.nextInt(99)+1;
					//numY = numY/10;
					numX = rGen.nextInt(99)+1;
					//numX = numX/10;
					for(int i=waterXArray.get(n)-m; i<=waterXArray.get(n)+m; i++)
					{
						for(int j=waterYArray.get(n)-m; j<=waterYArray.get(n)+m; j++)
						{
							if(j<0 || i<0 || j>=environGrid.length || i>=environGrid[0].length || environGrid[j][i].name.contains("Water")  || FindSimilar(environGrid,j,i,"Water")<1)
							{
								
							}
							else
							{
								num = rGen.nextInt(y0);
								// find x and y distance from center
								xDist = Math.abs(i-waterXArray.get(n));
								yDist = Math.abs(j-waterYArray.get(n));
								Dist = Math.sqrt(Math.pow(xDist*numX,2)+Math.pow(yDist*numY,2));
								factor = Math.pow(y0-50, 2)-1;
								factor2 = (m+offset)*factor/(numLoops);
								factor3 = y0/Math.sqrt(factor2+1);
								factorX = (xDist*numX)/Dist;
								factorY =(yDist*numY)/Dist;
								factor4 = (factorX*factor3 + factorY*factor3);
								if(num < factor4)
								{// Proc
									environGrid[j][i] = new Water(i,j);
								}
							}
						}
					}
				}
			}
		}
	}
	
	/**
	 * Generates a population.  should probably be done elsewhere.
	 * @param x
	 * @param y
	 * @param genLimit
	 */
	public void GeneratePopulation(int x, int y, int genLimit)
	{
		for(int i=0; i<x; i++)
		{
			for(int j=0; j<y; j++)
			{
				// generate the population
				num = generator.nextInt(genLimit);
				if(num == 1 && environGrid[j][i].isHabitable)
				{
					grid[j][i] = new Cell(i,j,true);
				}
				else
				{
					grid[j][i] = new Cell(i,j,false);
				}				
			}
		}
	}
	
	/**
	 * Generates the inhabitants of the world.
	 * @param width
	 * @param height
	 * @param genLimit
	 * @param totalPop
	 */
	public void GenerateInhabitants(int width, int height, int genLimit, int totalPop)
	{
		int count = 0;
		boolean complete = false;
		
		for (int i=0; i<totalPop; i++)
			population[i] = new Person(-1,-1);
		
		while (!complete)
		{
			for(int i=0; i<width; i++)
			{
				for(int j=0; j<height; j++)
				{
					// generate the population
					num = generator.nextInt(genLimit);
					if(num == 1 && environGrid[j][i].isHabitable && FindInhabitant(i, j).getX() == -1)
					{
						population[count] = new Person(i,j);
						count++;
					}			
					if (count >= totalPop)
					{
						i=width;
						j=height;
					}
				}
			}
			if(count >= totalPop)
			{
				complete=true;
			}
		}
	}
	
	/**
	 * Removes a block that has no similar neighbors.  Anomalies
	 * @param block - the block to look for 
	 */
	public void RemoveLonelyBlock(String block)
	{
		for(int i=0; i<environGrid[0].length; i++)
		{
			for(int j=0; j<environGrid.length; j++)
			{
				if(environGrid[j][i].name.contains(block))
				{
					int temp = FindSimilar(environGrid, j, i, block);
					if(temp<=2)
					{	// replace with most popular block
						if(FindSimilar(environGrid, j, i, "Grass")>=FindSimilar(environGrid, j, i, "Water"))
							environGrid[j][i]=new Grass(i,j);
						else
							environGrid[j][i]=new Water(i,j);
					}
				}
			}
		}
	}
	
	/**
	 * Determines the distance to a specific block type.
	 */
	public double DistanceToEnvironment(int x, int y, String env)
	{
		double distance = 99999, tempDistance=0;
		int[] loc = {0,0};
		if(environGrid[y][x].name.contains(env))
		{
			return 0;
		}
		else
		{
			for(int i=x-40; i<x+40; i++)
			{
				for(int j=y-40; j<y+40; j++)
				{
					if(i<0 || j<0 || i>=environGrid[0].length || j>=environGrid.length)
					{
						
					}
					else
					{
						if(environGrid[j][i].name.contains(env))
						{
							tempDistance = Math.sqrt(Math.pow(j-y, 2)+Math.pow(i-x, 2));
							if(tempDistance < distance)
							{
								distance = tempDistance;
								loc[0]=i;
								loc[1]=j;
							}
						}
					}
				}
			}
		}
		return distance;
		
	}
	

}
